package ru.classbase.dbdumper.writer

import com.opencsv.CSVWriterBuilder
import com.opencsv.ResultSetHelperService
import org.springframework.stereotype.Component
import ru.classbase.dbdumper.AppProperties
import ru.classbase.dbdumper.model.Column
import ru.classbase.dbdumper.model.DbMetaData
import ru.classbase.dbdumper.model.DumpContext
import ru.classbase.dbdumper.model.OutputFormat
import java.sql.ResultSet
import java.time.format.DateTimeFormatter


@Component
class CsvWriter(
    private val props: AppProperties,
    private val dateFormatter: DateTimeFormatter,
    private val dateTimeFormatter: DateTimeFormatter,
) : DumpWriter {

    override val supportedFormat =  OutputFormat.CSV


    override fun write(metaData: DbMetaData, context: DumpContext) {

        val (_, _, columns, rs, writer) = context

        val helper = ResultSetHelperService()
        helper.setDateFormat(props.dateFormat)
        helper.setDateTimeFormat(props.dateTimeFormat)

        val csv = CSVWriterBuilder(writer)
            .withLineEnd(props.csv.lineEnd)
            .withSeparator(props.csv.separator)
            .withQuoteChar(props.csv.quoteChar)
            .withEscapeChar(props.csv.escapeChar)
            .withResultSetHelper(helper)
            .build()

        csv.use {
            if (props.csv.includeColumnNames) {
                val columnNames = columns.map { col -> col.columnName }.toTypedArray()
                it.writeNext(columnNames)
            }

            while (rs.next()) {
                it.writeNext(getNext(rs, columns))
            }
        }
    }

    private fun getNext(rs: ResultSet, columns: List<Column>): Array<String> {
        val result = Array(columns.size) { idx ->
            val i = idx + 1
            when (columns[idx].dataType) {
                "integer" -> handleInt(rs, i)
                "date" -> handleDate(rs, i)
                "smallint" -> handleSmallInt(rs, i)
                "timestamp without time zone" -> handleTimestamp(rs, i)
                "boolean" -> handleBoolean(rs, i)
                "character varying" -> handleString(rs, i)
                "numeric" -> handleNumeric(rs, i)
                "bigint" -> handleBigInt(rs, i)
                else -> throw IllegalArgumentException("Тип данных [${columns[i].dataType}]  не поддерживается")
            }

        }

        return result
    }

    fun handleString(rs: ResultSet, colNum: Int): String {
        val value = rs.getString(colNum)

        if (rs.wasNull()) {
            return ""
        }

        return value
    }

    fun handleBoolean(rs: ResultSet, colNum: Int): String {
        val value = rs.getBoolean(colNum)

        if (rs.wasNull()) {
            return ""
        }

        return value.toString()
    }

    fun handleSmallInt(rs: ResultSet, colNum: Int): String {
        val value = rs.getShort(colNum)

        if (rs.wasNull()) {
            return ""
        }

        return value.toString()
    }

    fun handleInt(rs: ResultSet, colNum: Int): String {
        val value = rs.getInt(colNum)

        if (rs.wasNull()) {
            return ""
        }

        return value.toString()
    }

    fun handleBigInt(rs: ResultSet, colNum: Int): String {
        val value = rs.getLong(colNum)

        if (rs.wasNull()) {
            return ""
        }

        return value.toString()
    }

    fun handleNumeric(rs: ResultSet, colNum: Int): String {
        val value = rs.getBigDecimal(colNum)

        if (rs.wasNull()) {
            return ""
        }
        return value.toPlainString()
    }

    fun handleDate(rs: ResultSet, colNum: Int): String {
        val value = rs.getDate(colNum)

        if (rs.wasNull()) {
            return ""
        }

        return dateFormatter.format(value.toLocalDate())
    }

    fun handleTimestamp(rs: ResultSet, colNum: Int): String {
        val value = rs.getTimestamp(colNum)

        if (rs.wasNull()) {
            return ""
        }

        return dateTimeFormatter.format(value.toLocalDateTime())
    }
}